﻿/*
 *  Author: Louis A. Leskoviansky
 *  Company: Artemis Solutions Group (http://www.artemis-solutions.com)
 */


using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Web;
using System.Web.UI;
using System.Web.UI.HtmlControls;
using System.Web.UI.WebControls;

namespace AjaxControlFramework
{
    public static class RenderingHelper
    {
        //------// Properties \\--------------------------------------------\\
        public delegate bool HasRenderMethod(Control control);
        public delegate object ControlRareFieldsProperty(Control control);
        public delegate RenderMethod GetRenderMethod(object control);

        public delegate void RenderChildren(HtmlTextWriter writer, System.Web.UI.Control c);


        private static object HasRenderDelegateLock = new object();
        private static HasRenderMethod _hasRenderDelegate = null;
        public static HasRenderMethod HasRenderDelegate
        {
            get
            {
                if (_hasRenderDelegate == null)
                {
                    lock (HasRenderDelegateLock)
                    {
                        if (_hasRenderDelegate == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET internal method "HasRenderDelegate".
                            MethodInfo hasRenderDelegateMethodInfo = TypeOfControl.GetMethod("HasRenderDelegate", BindingFlags.NonPublic | BindingFlags.Instance);
                            DynamicMethod dynamicMethod = new DynamicMethod("HasRenderDelegate", typeof(System.Boolean), new Type[] { TypeOfControl }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Call, hasRenderDelegateMethodInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _hasRenderDelegate = (HasRenderMethod)dynamicMethod.CreateDelegate(typeof(HasRenderMethod));
                        }
                    }
                }

                return _hasRenderDelegate;
            }
        }


        private static Type TypeOfControl = typeof(System.Web.UI.Control);


        private static object ControlRareFieldsTypeLock = new object();
        private static Type _controlRareFieldsType = null;
        public static Type ControlRareFieldsType
        {
            get
            {
                if (_controlRareFieldsType == null)
                {
                    lock (ControlRareFieldsTypeLock)
                    {
                        if (_controlRareFieldsType == null)
                        {
                            // Initialize and cache the ASP.NET private type "ControlRareFields".
                            _controlRareFieldsType = System.Web.Compilation.BuildManager.GetType("System.Web.UI.Control+ControlRareFields, System.Web, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", true);
                        }
                    }
                }

                return _controlRareFieldsType;
            }
        }


        private static object RareFieldsEnsuredLock = new object();
        private static ControlRareFieldsProperty _rareFieldsEnsured = null;
        public static ControlRareFieldsProperty RareFieldsEnsured
        {
            get
            {
                if (_rareFieldsEnsured == null)
                {
                    lock (RareFieldsEnsuredLock)
                    {
                        if (_rareFieldsEnsured == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET private property "RareFieldsEnsured".
                            MethodInfo rareFieldsEnsuredMethodInfo = TypeOfControl.GetProperty("RareFieldsEnsured", BindingFlags.Instance | BindingFlags.NonPublic).GetGetMethod(true);
                            DynamicMethod dynamicMethod = new DynamicMethod("RareFieldsEnsured", ControlRareFieldsType, new Type[] { TypeOfControl }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Call, rareFieldsEnsuredMethodInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _rareFieldsEnsured = (ControlRareFieldsProperty)dynamicMethod.CreateDelegate(typeof(ControlRareFieldsProperty));
                        }
                    }
                }

                return _rareFieldsEnsured;
            }
        }


        private static object GetRenderDelegateLock = new object();
        private static GetRenderMethod _getRenderDelegate = null;
        public static GetRenderMethod GetRenderDelegate
        {
            get
            {
                if (_getRenderDelegate == null)
                {
                    lock (GetRenderDelegateLock)
                    {
                        if (_getRenderDelegate == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET public field "RenderMethod" of the System.Web.UI.Control.ControlRareFields private class.
                            FieldInfo renderMethodFieldInfo = ControlRareFieldsType.GetField("RenderMethod", BindingFlags.Public | BindingFlags.Instance);
                            DynamicMethod dynamicMethod = new DynamicMethod("RenderMethod", typeof(System.Web.UI.RenderMethod), new Type[] { typeof(object) }, ControlRareFieldsType.Module, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Ldfld, renderMethodFieldInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _getRenderDelegate = (GetRenderMethod)dynamicMethod.CreateDelegate(typeof(GetRenderMethod));
                        }
                    }
                }

                return _getRenderDelegate;
            }
        }


        public delegate string GetReadOnlyErrorMsgMethod(ControlCollection control);

        private static object GetReadOnlyErrorMsgLock = new object();
        private static GetReadOnlyErrorMsgMethod _getReadOnlyErrorMsg = null;
        public static GetReadOnlyErrorMsgMethod GetReadOnlyErrorMsg
        {
            get
            {
                if (_getReadOnlyErrorMsg == null)
                {
                    lock (GetReadOnlyErrorMsgLock)
                    {
                        if (_getReadOnlyErrorMsg == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET private field "_readOnlyErrorMsg" of the System.Web.UI.ControlCollection class.
                            FieldInfo readOnlyErrorMessageFieldInfo = typeof(ControlCollection).GetField("_readOnlyErrorMsg", BindingFlags.NonPublic | BindingFlags.Instance);
                            DynamicMethod dynamicMethod = new DynamicMethod("_getReadOnlyErrorMsg", typeof(string), new Type[] { typeof(ControlCollection) }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Ldfld, readOnlyErrorMessageFieldInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _getReadOnlyErrorMsg = (GetReadOnlyErrorMsgMethod)dynamicMethod.CreateDelegate(typeof(GetReadOnlyErrorMsgMethod));
                        }
                    }
                }

                return _getReadOnlyErrorMsg;
            }
        }


        public delegate void SetReadOnlyErrorMsgMethod(ControlCollection control, string errorMessage);

        private static object SetReadOnlyErrorMsgLock = new object();
        private static SetReadOnlyErrorMsgMethod _setReadOnlyErrorMsg = null;
        public static SetReadOnlyErrorMsgMethod SetReadOnlyErrorMsg
        {
            get
            {
                if (_setReadOnlyErrorMsg == null)
                {
                    lock (SetReadOnlyErrorMsgLock)
                    {
                        if (_setReadOnlyErrorMsg == null)
                        {
                            // Initialize and cache the dynamic method delegate to the ASP.NET private field "_readOnlyErrorMsg" of the System.Web.UI.ControlCollection class.
                            FieldInfo readOnlyErrorMessageFieldInfo = typeof(ControlCollection).GetField("_readOnlyErrorMsg", BindingFlags.NonPublic | BindingFlags.Instance);
                            DynamicMethod dynamicMethod = new DynamicMethod("_setReadOnlyErrorMsg", typeof(void), new Type[] { typeof(ControlCollection), typeof(string) }, true);

                            ILGenerator ilGenerator = dynamicMethod.GetILGenerator();
                            ilGenerator.Emit(OpCodes.Ldarg_0);
                            ilGenerator.Emit(OpCodes.Ldarg_1);
                            ilGenerator.Emit(OpCodes.Stfld, readOnlyErrorMessageFieldInfo);
                            ilGenerator.Emit(OpCodes.Ret);

                            _setReadOnlyErrorMsg = (SetReadOnlyErrorMsgMethod)dynamicMethod.CreateDelegate(typeof(SetReadOnlyErrorMsgMethod));
                        }
                    }
                }

                return _setReadOnlyErrorMsg;
            }
        }


        public delegate void BeforeRendering(); // This has no parameters...use closures.
        //------\\ Properties //--------------------------------------------//



        //------// Methods \\-----------------------------------------------\\
        public static void RenderControlWithMarkers(Control control, BeforeRendering beforeRendering)
        {
            // Sorry. This is kind of dirty...but fast. This crazy logic helps to ensure the 
            // "The Controls collection cannot be modified because the control contains code blocks" 
            // error doesn't happen to you.
            if (RenderingHelper.HasRenderDelegate(control))
            {
                RenderMethod existingRenderMethod = RenderingHelper.GetRenderDelegate(RenderingHelper.RareFieldsEnsured(control));

                if (existingRenderMethod != null)
                {
                    SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                    {
                        if (beforeRendering != null) { beforeRendering(); }

                        writer.Write("<!--" + c.UniqueID + "_start-->");
                        existingRenderMethod(writer, c);
                        writer.Write("<!--" + c.UniqueID + "_end-->");
                    }), control);
                }
                else
                {
                    SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                    {
                        if (beforeRendering != null) { beforeRendering(); }

                        writer.Write("<!--" + c.UniqueID + "_start-->");
                        RenderChildControls(c, writer);
                        writer.Write("<!--" + c.UniqueID + "_end-->");
                    }), control);
                }
            }
            else
            {
                SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                {
                    if (beforeRendering != null) { beforeRendering(); }

                    writer.Write("<!--" + c.UniqueID + "_start-->");
                    RenderChildControls(c, writer);
                    writer.Write("<!--" + c.UniqueID + "_end-->");
                }), control);
            }
        }


        public static void RenderScriptBlocksToBody(List<ScriptReference> scriptReferences, Page page)
        {
            if (page == null || page.IsCallback) { return; }

            List<HtmlControl> controls = AjaxControlUtils.FindChildControlsByTagName(page, "body");

            HtmlControl body = null;

            if (controls != null && controls.Count > 0)
            {
                body = controls[0];
            }

            if (body != null)
            {
                if (RenderingHelper.HasRenderDelegate(body))
                {
                    RenderMethod existingRenderMethod = RenderingHelper.GetRenderDelegate(RenderingHelper.RareFieldsEnsured(body));

                    if (existingRenderMethod != null)
                    {
                        SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                        {
                            existingRenderMethod(writer, c);
                            RenderScriptBlocks(scriptReferences, writer);
                        }), body);
                    }
                    else
                    {
                        SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                        {
                            RenderChildControls(c, writer);
                            RenderScriptBlocks(scriptReferences, writer);
                        }), body);
                    }
                }
                else
                {
                    SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                    {
                        RenderChildControls(c, writer);
                        RenderScriptBlocks(scriptReferences, writer);
                    }), body);
                }
            }
        }


        public static void RenderScriptBlocksToBody(List<ScriptReference> scriptReferences)
        {
            RenderScriptBlocksToBody(scriptReferences, HttpContext.Current.CurrentHandler as Page);
        }


        public static void RenderScriptBlocksToHead(List<ScriptReference> scriptReferences, Page page)
        {
            if (page == null || page.IsCallback) { return; }

            HtmlHead head = page.Header;

            if (head != null)
            {
                if (RenderingHelper.HasRenderDelegate(head))
                {
                    RenderMethod existingRenderMethod = RenderingHelper.GetRenderDelegate(RenderingHelper.RareFieldsEnsured(head));

                    if (existingRenderMethod != null)
                    {
                        SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                        {
                            existingRenderMethod(writer, c);
                            RenderScriptBlocks(scriptReferences, writer);
                        }), head);
                    }
                    else
                    {
                        SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                        {
                            RenderChildControls(c, writer);
                            RenderScriptBlocks(scriptReferences, writer);
                        }), head);
                    }
                }
                else
                {
                    SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                    {
                        RenderChildControls(c, writer);
                        RenderScriptBlocks(scriptReferences, writer);
                    }), head);
                }
            }
        }


        public static void RenderScriptBlocksToHead(List<ScriptReference> scriptReferences)
        {
            RenderScriptBlocksToHead(scriptReferences, HttpContext.Current.CurrentHandler as Page);
        }


        public static void RenderScriptBlocksAfterControl(List<ScriptReference> scriptReferences, Control control)
        {
            if (control == null || control.Page == null || control.Page.IsCallback) { return; }

            if (RenderingHelper.HasRenderDelegate(control))
            {
                RenderMethod existingRenderMethod = RenderingHelper.GetRenderDelegate(RenderingHelper.RareFieldsEnsured(control));

                if (existingRenderMethod != null)
                {
                    SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                    {
                        existingRenderMethod(writer, c);
                        RenderScriptBlocks(scriptReferences, writer); // Creating a closure around 'scriptReferences'.
                    }), control);
                }
                else
                {
                    SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                    {
                        RenderChildControls(c, writer);
                        RenderScriptBlocks(scriptReferences, writer); // Creating a closure around 'scriptReferences'.
                    }), control);
                }
            }
            else
            {
                SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                {
                    RenderChildControls(c, writer);
                    RenderScriptBlocks(scriptReferences, writer); // Creating a closure around 'scriptReferences'.
                }), control);
            }
        }


        public static void RenderScriptBlocksBeforeControl(List<ScriptReference> scriptReferences, Control control)
        {
            if (control == null || control.Page == null || control.Page.IsCallback) { return; }

            if (RenderingHelper.HasRenderDelegate(control))
            {
                RenderMethod existingRenderMethod = RenderingHelper.GetRenderDelegate(RenderingHelper.RareFieldsEnsured(control));

                if (existingRenderMethod != null)
                {
                    SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                    {
                        RenderScriptBlocks(scriptReferences, writer); // Creating a closure around 'scriptReferences'.
                        existingRenderMethod(writer, c);
                    }), control);
                }
                else
                {
                    SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                    {
                        RenderScriptBlocks(scriptReferences, writer); // Creating a closure around 'scriptReferences'.
                        RenderChildControls(c, writer);
                    }), control);
                }
            }
            else
            {
                SetRenderMethodDelegate(new RenderChildren(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
                {
                    RenderScriptBlocks(scriptReferences, writer); // Creating a closure around 'scriptReferences'.
                    RenderChildControls(c, writer);
                }), control);
            }
        }


        public static void SetRenderMethodDelegate(RenderChildren renderChildrenDelegate, System.Web.UI.Control control)
        {
            string previousErrorMsg = null;

            control.SetRenderMethodDelegate(new RenderMethod(delegate(HtmlTextWriter writer, System.Web.UI.Control c)
            {
                renderChildrenDelegate(writer, c);

                if (previousErrorMsg != null) // Creating a closure around 'previousErrorMsg'.
                {
                    SetReadOnlyErrorMsg(control.Controls, previousErrorMsg);
                }
            }));

            previousErrorMsg = GetReadOnlyErrorMsg(control.Controls);
            if (previousErrorMsg != null)
            {
                SetReadOnlyErrorMsg(control.Controls, null);
            }
        }


        public static void RenderChildControls(Control parentControl, HtmlTextWriter writer)
        {
            if (parentControl == null || parentControl.Controls.Count == 0 || writer == null) { return; }

            foreach (Control childControl in parentControl.Controls)
            {
                if (childControl is LiteralControl)
                {
                    writer.Write((childControl as LiteralControl).Text);
                }
                else
                {
                    childControl.RenderControl(writer);
                }
            }
        }


        public static void RenderScriptBlocks(List<ScriptReference> scriptReferences, HtmlTextWriter writer)
        {
            if (scriptReferences == null || scriptReferences.Count == 0 || writer == null) { return; }

            ScriptReference[] references = scriptReferences.ToArray<ScriptReference>();

            // Sort the ScriptReference objects according to their dependencies.
            if (references.Length > 1)
            {
                ScriptReferenceDependencyComparer comparer = new ScriptReferenceDependencyComparer();

                // Good ol' bubble sort.
                for (int index1 = 0; index1 < references.Length - 1; index1++)
                {
                    for (int index2 = index1 + 1; index2 < references.Length; index2++)
                    {
                        int comparison = comparer.Compare(references[index1], references[index2]);

                        if (comparison >= 1)
                        {
                            // Swap.
                            ScriptReference temp = references[index1];
                            references[index1] = references[index2];
                            references[index2] = temp;
                        }
                    }
                }
            }

            foreach (ScriptReference reference in references)
            {
                if (!reference.IsRendered)
                {
                    writer.WriteLine(reference.GenerateScriptBlock());
                    reference.IsRendered = true;
                }
            }
        }
        //------\\ Methods //-----------------------------------------------//
    }
}
